/*eslint-disable*/
const fs = require('fs')
const fsPromises = fs.promises
const path = require('path')
const createHtmlDom = require('htmldom')
const babel = require('@babel/core')
const uglifycss = require('uglifycss')
const execProcess = require('child_process')
const readlineSync = require('readline-sync')

//取出第二个参数，做为打包参数，只接受all - 打包所有  last - 打包最近一次  prod - 打包生产
let argv = process.argv[2]
let compileDir = ['./widget/', './common/', './pages/']

if (!['all', 'last', 'custom', 'prod', 'cp', 'test'].includes(argv))
  argv = 'all'

//删除dist目录
try {
  emptyDir(path.resolve(__dirname, './dist'))
} catch (e) {}

//设置一个白名单，主要用于不打包第三方插件，防止打出问题
const whiteFileList = ['vant', 'vue.min', 'eruda.min', 'aliplayer']

//根据传入的参数决定怎么打包
switch (argv) {
  case 'all':
    // 打包所有
    //获取文件夹下的所有图片
    let buildEnv = readlineSync.question(
      'please input build env( develop|test|cp|prod):'
    )
    argv = buildEnv || 'develop'

    let fileList = []

    compileDir.forEach(dir => {
      let files = getAllFileList(dir)
      fileList.push(...files.compileFiles, ...files.otherFiles)
    })

    startCompile(fileList)
    break
  case 'last':
    //获取最近一次提交的log修改的所有文件，windows不支持grep后续操作，只能手动截取
    execProcess.exec('git log -1 --name-only', function(error, stdout, stderr) {
      if (error) {
        console.log('获取最后一次git log失败')
        return
      }

      try {
        //取出修改文件部分，第⑤行开始
        let fileList = stdout
          .split('\n')
          .filter(item => item)
          .slice(4)
        startCompile(fileList)
      } catch (e) {
        console.log('获取最后一次git log失败')
      }
    })
    break
  case 'custom':
    //打包某两次log之间的所有的修改文件
    //打包生产的情况下，读取用户输入的log
    var startLog = readlineSync.question('please input lastest prod log:')
    if (!startLog) {
      console.log('log不能为空!')
      break
    }

    var endLog = readlineSync.question('please input newest log:')
    if (!endLog) {
      console.log('log不能为空!')
      break
    }

    //获取两次log之间的所有修改过的提交
    execProcess.exec(`git diff ${startLog} ${endLog} --name-only`, function(
      error,
      stdout,
      stderr
    ) {
      if (error) {
        console.log('获取两次log之间的所有修改过的文件操作失败')
        return
      }

      try {
        //取出修改文件部分，第⑤行开始
        let fileList = stdout.split('\n')
        startCompile(fileList)
      } catch (e) {
        console.log('获取最后一次git log失败')
      }
    })
    break
  case 'test':
    //打包测试环境的代码，develop分支上打包
    execProcess.exec(`git diff develop origin/master --name-only`, function(
      error,
      stdout,
      stderr
    ) {
      if (error) {
        console.log(`获取develop与master所有修改过的文件操作失败`)
        return
      }

      try {
        //取出修改文件部分，第⑤行开始
        let fileList = stdout.split('\n').filter(item => {
          return compileDir.some(citem => {
            return item.includes(citem.replace('./', ''))
          })
        })

        console.log(`start build test...`)

        startCompile(fileList)
      } catch (e) {
        console.log('操作失败')
      }
    })
    break
  //如果是打包这两个环境，必须先切换到master分支，从master分支打包
  //判断最近一次tag与当前master修改过的所有文件，打包
  case 'cp':
  case 'prod':
    let env = {
      cp: 'pre-production',
      prod: 'production'
    }

    //获取当前所在分支，只能在master打包
    execProcess.exec('git branch', (error, stdout) => {
      if (error) return console.log('获取所在分支失败')

      try {
        let branch = stdout.split('\n').find(item => item.includes('*'))

        if (!branch.includes('master'))
          return console.log('当前不在master分支，请在master分支进行本操作！')

        //获取到最近的tag
        execProcess.exec('git describe --tag', function(error, stdout) {
          if (error) {
            console.log('获取最后一次tag失败！')
            return
          }

          try {
            //获取到了最后一个tag
            let lastTag = stdout.split('-')[0]

            console.log(`最近一次tag是：${lastTag}`)

            //对比最后一次tag到现在的所有修改文件
            execProcess.exec(`git diff ${lastTag} HEAD --name-only`, function(
              error1,
              stdout1
            ) {
              if (error1) {
                console.log(`获取master最近所有修改过的文件操作失败`)
                return
              }

              try {
                //取出修改文件部分，第⑤行开始
                let fileList = stdout1.split('\n').filter(item => {
                  return compileDir.some(citem => {
                    return item.includes(citem.replace('./', ''))
                  })
                })

                console.log(`start build ${env[argv]}...`)

                startCompile(fileList)
              } catch (e) {
                console.log('操作失败')
              }
            })
          } catch (e) {
            console.log('解析当前分支最后一次tag失败')
          }
        })
      } catch (e) {
        console.log('解析当前所在分支失败！')
      }
    })

    break
}

//开始编译
function startCompile(fileList) {
  fileList.forEach(file => {
    let isWhiteFile = whiteFileList.some(item => file.includes(item))

    //如果是白名单中的文件，不处理直接复制过去
    if (isWhiteFile) return copyFile(file)

    if (file && typeof file === 'string') readFile(file)
  })

  console.log(`本次打包${fileList.length}文件`)
}

async function readFile(file) {
  let fileReadRes = await fsPromises
    .readFile(path.join(__dirname, file), {
      encoding: 'utf-8'
    })
    .catch(err => '')

  //如果没读成功，表示出错了
  if (!fileReadRes) return

  compile(fileReadRes, file)
}

//编译入口，只处理html\css\js文件
function compile(fileRead, file) {
  //读取成功后，分几种文件进行处理
  let ext = path.extname(file)

  switch (ext) {
    case '.js':
      compileJs(fileRead, file)
      break
    case '.css':
      compileCss(fileRead, file)
      break
    case '.html':
      compileHtml(fileRead, file)
      break
    default:
      //如果不是这三种文件，不做任何处理，直接复制到dist目录
      copyFile(file)
      break
  }
}

//编译纯js文件
//主要进行babel转换，es6-es5
//其次进行压缩
async function compileJs(jsStr, file) {
  let compileStr = babelTransform(jsStr)

  //修改对应环境的操作，如修改api地址等，暂时先放在这里
  compileStr = modifyEnvCode(compileStr, file)

  //复制到dist目录
  outPut(file, compileStr)
}
//编译纯css文件
//主要是进行压缩
function compileCss(cssStr, file) {
  outPut(file, minifyCss(cssStr))
}

//编译纯html文件
//主要工作
//1、取出js部分，编译压缩去注释
//2、取出style部分，压缩
//3、html部分，压缩
async function compileHtml(htmlStr, file) {
  let $ = createHtmlDom(htmlStr)

  let findScript = []

  //获取非外链script
  $('script').each((index, item) => {
    let src = $(item).attr('src')

    if (!src) {
      findScript.push(item)
    }
  })

  let tasks = []

  //执行编译
  findScript.forEach(item => {
    tasks.push(
      new Promise(async (resolve, reject) => {
        let str = $(item).html()

        let compileStr = babelTransform(str)

        $(item).html(compileStr)

        resolve(compileStr)
      })
    )
  })

  let res = await Promise.all(tasks)

  //压缩代码，主要是html及css
  let miniStr = minify($)

  //输出文件
  outPut(file, miniStr)
}

//babel编译压缩js代码
function babelTransform(scriptStr) {
  return babel.transformSync(scriptStr, {
    plugins: [
      'object-entries-and-values-to-object-keys',
      'transform-object-assign',
      'es6-promise'
    ], //编译object.assign
    presets: [
      ['@babel/preset-env'],
      [
        'minify',
        {
          infinity: true
        }
      ]
    ], //压缩
    comments: false //去注释
  }).code
}

//html文件使用此函数，压缩css + html
function minify($) {
  //压缩css
  let findCss = []

  //获取非外链script
  $('style').each((index, item) => {
    let src = $(item).attr('src')
    if (!src) {
      findCss.push(item)
    }
  })

  //压缩css
  findCss.forEach(item => $(item).html(minifyCss($(item).html())))
  //压缩html
  return $.uglify({
    removeAttributeQuotes: true
  })
}

//压缩css
function minifyCss(cssStr) {
  return uglifycss.processString(cssStr)
}

//输出新的文件
function outPut(file, newFileStr = '') {
  //输出
  //创建目录
  let outDir = file.slice(0, file.lastIndexOf('/'))

  fsPromises
    .mkdir(path.join(__dirname, './dist/', outDir), { recursive: true })
    .then(() => {
      fsPromises.writeFile(path.join(__dirname, './dist/', file), newFileStr)
    })
    .catch(err => {
      console.log('文件输出错误：' + file)
    })
}

function readFileList(path, filesList) {
  let files = fs.readdirSync(path)
  files.forEach(function(itm, index) {
    let stat = fs.statSync(path + itm)
    if (stat.isDirectory()) {
      //递归读取文件
      readFileList(path + itm + '/', filesList)
    } else {
      let obj = {} //定义一个对象存放文件的路径和名字
      obj.path = path //路径
      obj.filename = itm //名字
      filesList.push(obj)
    }
  })
}

//获取文件夹下的所有文件
function getFileList(path) {
  let filesList = []
  readFileList(path, filesList)
  return filesList
}
//获取指定目录下所有文件
function getAllFileList(opath) {
  let files = {
    compileFiles: [], //获取需要编译的文件
    otherFiles: [] //其他文件
  }

  getFileList(opath).forEach(item => {
    if (['.html', '.css', '.js'].includes(path.extname(item.filename))) {
      files.compileFiles.push(item.path + item.filename)
    } else {
      files.otherFiles.push(item.path + item.filename)
    }
  })
  return files
}

function emptyDir(path) {
  let arr = fs.readdirSync(path)

  for (let i in arr) {
    //读取文件信息，以便于判断是否是一个文件或目录
    let stats = fs.statSync(path + '/' + arr[i])

    if (stats.isFile()) {
      //判断为真，是文件则执行删除文件
      fs.unlinkSync(path + '/' + arr[i])
    } else {
      //判断为假就是文件夹，就调用自己，递归的入口
      emptyDir(path + '/' + arr[i])
    }
  }
  //删除空目录
  fs.rmdirSync(path)
}

//复制文件到目标位置
function copyFile(file) {
  let newPath = path.resolve('./dist/', file)
  if (newPath.includes('/')) {
    newPath = newPath.slice(0, newPath.lastIndexOf('/'))
  } else {
    newPath = newPath.slice(0, newPath.lastIndexOf('\\'))
  }

  fsPromises
    .mkdir(newPath, { recursive: true })
    .then(() => {
      return fsPromises.copyFile(file, path.resolve('./dist/', file))
    })
    .catch(err => console.log('拷贝失败:' + file))
}

//修改对应环境的代码
function modifyEnvCode(str, file) {
  let handleFile = ['common/js/http.js', 'common/js/common.js']

  let find = handleFile.some(item => file.includes(item))

  if (!find) return str

  argv = ['develop', 'test', 'cp', 'prod'].includes(argv) ? argv : 'develop'

  //处理common.js
  if (file.includes('common.js')) {
    let reg = /\.environment=\"\w+\"/
    //修改登录页链接
    //修改接口地址
    //移除按钮
    return str
      .replace(',document.body.appendChild(g)', '') //关闭退出登录按钮
      .replace(reg, `.environment="${argv}"`) //替换登录地址
  }

  return str
}
